package com.nadostar.extensions.util;

import java.io.Serializable;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.*;

/**
 * {@link ExtendedMap}의 기본 구현체
 *
 */
public class DataMap implements ExtendedMap, Serializable {
	static final long serialVersionUID = -5185735896348635052L;

	protected Map<Object, Object> map;

	/**
	 * Instantiates a new data map.
	 * 
	 * @param map the map
	 */
	public DataMap(Map<Object, Object> map) {
		if (map == null) {
			this.map = new LinkedHashMap<Object, Object>();
		} else {
			this.map = map;
		}
	}

	/**
	 * 주어진 문자열을 DataMap.parse로 파싱해서 맵에 넣는다.
	 */
	public DataMap(String string) {
		this.map = new LinkedHashMap<Object, Object>();
		parse(string);
	}

	/**
	 * Instantiates a new data map.
	 */
	public DataMap() {
		this.map = new LinkedHashMap<Object, Object>();
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#addAll(java.util.Map)
	 * {@inheritDoc}
	 */
	public void addAll(Map<?, ?> map) {
		if (map == null) {
			return;
		}
		for (Map.Entry entry : map.entrySet()) {
			Object value = entry.getValue();
			
			if (value == null) {
				continue;
			}
			
			Object toadd;
			
			if (value instanceof String[]) {
				String[] values = (String[])value;
			
				if (values.length > 1) {
					toadd = new ArrayList<Object>(Arrays.asList(values));
				} else {
					toadd = values[0];
				}
			} else {
				toadd = value.toString();
			}

			this.map.put(entry.getKey(), toadd);
		}
	}

	/**
	 * @see java.util.Map#size()
	 * {@inheritDoc}
	 */
	public int size() {
		return map.size();
	}

	/**
	 * @see java.util.Map#clear()
	 * {@inheritDoc}
	 */
	public void clear() {
		map.clear();
	}

	/**
	 * @see java.util.Map#isEmpty()
	 * {@inheritDoc}
	 */
	public boolean isEmpty() {
		return map.isEmpty();
	}

	/**
	 * @see java.util.Map#containsKey(java.lang.Object)
	 * {@inheritDoc}
	 */
	public boolean containsKey(Object key) {
		return map.containsKey(key);
	}

	/**
	 * @see java.util.Map#containsValue(java.lang.Object)
	 * {@inheritDoc}
	 */
	public boolean containsValue(Object key) {
		return map.containsValue(key);
	}

	/**
	 * @see java.util.Map#values()
	 * {@inheritDoc}
	 */
	public Collection<Object> values() {
		return map.values();
	}

	/**
	 * Put all.
	 * 
	 * @param subMap the sub map
	 */
	public void putAll(Map<?, ?> subMap) {
		map.putAll(subMap);
	}

	/**
	 * @see java.util.Map#entrySet()
	 * {@inheritDoc}
	 */
	public Set<Map.Entry<Object, Object>> entrySet() {
		return map.entrySet();
	}

	/**
	 * @see java.util.Map#keySet()
	 * {@inheritDoc}
	 */
	public Set<Object> keySet() {
		return map.keySet();
	}

	/**
	 * @see java.util.Map#get(java.lang.Object)
	 * {@inheritDoc}
	 */
	public Object get(Object key) {
		return map.get(key);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#get(java.lang.Object, java.lang.Object)
	 * {@inheritDoc}
	 */
	public Object get(Object key, Object defaultValue) {
		Object value = map.get(key);
		return value == null ? defaultValue : value;
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getString(java.lang.String)
	 * {@inheritDoc}
	 */
	public String getString(String key) {
		return getString(key, null);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getString(java.lang.String, java.lang.String)
	 * {@inheritDoc}
	 */
	public String getString(String key, String defaultValue) {
		String[] strs = getStrings(key);

		if (strs == null || strs[0] == null) {
			return defaultValue;
		}

		return strs[0];
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getStrings(java.lang.String)
	 * {@inheritDoc}
	 */
	public String[] getStrings(String name) {
		Object obj[] = getValues(name).toArray();

		if (obj == null || obj.length == 0 || obj[0] == null) {
			return null;
		}
		
		String[] strs = new String[obj.length];

		for (int i = 0; i < obj.length; i++) {
			strs[i] = String.valueOf(obj[i]);
		}

		return strs;
	}

	/**
	 * @see java.util.Map#remove(java.lang.Object)
	 * {@inheritDoc}
	 */
	public Object remove(Object key) {
		return map.remove(key);
	}

	/**
	 * @see java.util.Map#put(java.lang.Object, java.lang.Object)
	 * {@inheritDoc}
	 */
	public Object put(Object key, Object value) {
		return map.put(key, value);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getBoolean(java.lang.String)
	 * {@inheritDoc}
	 */
	public boolean getBoolean(String key) {
		return getBoolean(key, false);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getBoolean(java.lang.String, boolean)
	 * {@inheritDoc}
	 */
	public boolean getBoolean(String key, boolean defaultValue) {
		String value = getString(key);
		
		if (value == null) {
			return defaultValue;
		}

		if (value.equalsIgnoreCase("true")) {
			return true;
		} else if (value.equalsIgnoreCase("false")) {
			return false;
		}
		
		return defaultValue;
	}

	/**
	 * 이 메소드는 문자열 파라미터를 double로 변환하여 반환한다. 값이 null이거나 적절한 숫자 형식이 아닐 경우 default
	 * value를 반환한다. 그러므로, 이 메소드가 사용되는 파라미터는 숫자 Type에 대한 유효성 검사가 선행되어야 한다.
	 * @param key String
	 * @return double
	 */
	public double getDouble(String key) {
		return getDouble(key, 0.0);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getDouble(java.lang.String, double)
	 * {@inheritDoc}
	 */
	public double getDouble(String key, double defaultValue) {
		String value = getString(key);
		
		if (value == null || value.equals("")) {
			return defaultValue;
		}
		try {
			return Double.valueOf(value);
		} catch (Exception e) {
			e.getMessage();
		}
		
		return defaultValue;
	}

	/**
	 * @param key java.lang.String
	 * @return java.lang.String
	 */
	public float getFloat(String key) {
		return (float)getDouble(key, 0.0);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getFloat(java.lang.String, float)
	 * {@inheritDoc}
	 */
	public float getFloat(String key, float defaultValue) {
		return (float)getDouble(key, (double)defaultValue);
	}

	/**
	 * @param key java.lang.String
	 * @return int
	 */
	public int getInt(String key) {
		return getInt(key, 0);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getInt(java.lang.String, int)
	 * {@inheritDoc}
	 */
	public int getInt(String key, int defaultValue) {
		return (int)getDouble(key, (double)defaultValue);
	}

	/**
	 * @param key java.lang.String
	 * @return java.lang.String
	 */
	public long getLong(String key) {
		return getLong(key, 0L);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getLong(java.lang.String, long)
	 * {@inheritDoc}
	 */
	public long getLong(String key, long defaultValue) {
		Object value = map.get(key);

		if (value == null) {
			return defaultValue;
		}
		if (value instanceof Timestamp) {
			return ((Timestamp)value).getTime();
		}
		if (value instanceof Date) {
			return ((Date)value).getTime();
		}
		if (value instanceof Number) {
			return ((Number)value).longValue();
		}

		String sValue = value.toString();
		
		if (sValue.equals("")) {
			return defaultValue;
		}
		
		return Long.parseLong(sValue);
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getValues(java.lang.String)
	 * {@inheritDoc}
	 */
	public List getValues(String key) {
		Object value = map.get(key);
		
		if (value instanceof List) {
			return (List)value;
		}
		
		List<Object> list = new ArrayList<Object>();
		
		if (value != null) {
			list.add(value);
		}
		
		return list;
	}

	/**
	 * @see java.lang.Object#toString()
	 * {@inheritDoc}
	 */
	public String toString() {
		return map.toString();
	}

	/**
	 * @see java.lang.Object#clone()
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public Object clone() throws CloneNotSupportedException {
		if (map instanceof HashMap) {
			return new DataMap((Map)((HashMap)map).clone());
		}
		if (map instanceof TreeMap) {
			return new DataMap((Map)((TreeMap)map).clone());
		}
		
		return null;
	}

	/**
	 * @see com.roguedevelopment.extensions.util.nhncorp.lucy.common.util.ExtendedMap#getInts(java.lang.String)
	 * {@inheritDoc}
	 */
	public int[] getInts(String name) {
		String[] strs = getStrings(name);
		
		if (strs == null) {
			return null;
		}
		
		int[] ints = new int[strs.length];
		
		for (int i = 0; i < strs.length; i++) {
			ints[i] = Integer.parseInt(strs[i]);
		}
		
		return ints;
	}

	/**
	 * "key1=value1, key2=value2, key3=value3"와 같은 형식으로 들어온 문자열을 파싱해서 맵에 넣는다.
	 *
	 * @param string String
	 */
	public void parse(String string) {
		String values[] = string.split(",");
		
		for (String value : values) {
			String pair[] = value.trim().split("=");
			
			if (pair.length > 1) {
				map.put(pair[0].trim(), pair[1].trim());
			} else {
				map.put(pair[0].trim(), "");
			}
		}
	}

	/**
	 * @see java.lang.Object#hashCode()
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		return map.hashCode();
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object obj) {
		return map.equals(obj);
	}
}